package main

import (
	"sync"
	"time"

	"github.com/kpango/glg"
	crono "github.com/utahta/go-cronowriter"
)

type Controller struct {
	mu       sync.RWMutex
	tables   map[string]Store
	logger   *crono.CronoWriter
	basepath string
	dbname   string
	logname  string
	buffer   RowSet
	closed   chan bool
}

// addr:source:state:time
type RowSet map[string]map[string]map[string]string

func (c *Controller) Setup() error {
	c.mu = sync.RWMutex{}
	c.buffer = make(RowSet)
	c.logger = crono.MustNew(c.basepath+"/"+c.logname+".%Y%m", crono.WithInit())
	glg.Get().SetMode(glg.WRITER).AddWriter(c.logger)
	c.tables = make(map[string]Store)
	db, err := NewStore(c.basepath, c.dbname)
	if err != nil {
		glg.Errorf("Fail to open store %v/%v, error:%v\n", c.basepath, c.dbname, err)
		return err
	}
	c.tables[c.dbname] = db
	go c.BatchWriteHandle()
	return nil
}

func (c *Controller) Close() error {
	go func() {
		c.closed <- true
	}()
	c.BatchPut()
	if err := c.tables[c.dbname].Close(); err != nil {
		glg.Errorf("Fail to close store %v/%v, error:%v\n", c.basepath, c.dbname, err)
		return err
	}
	if err := c.logger.Close(); err != nil {
		glg.Errorf("Fail to close logger %v, error:%v\n", c.logger.Path(), err)
		return err
	}
	return nil
}

func (c *Controller) BatchWriteHandle() {
	tick := time.Tick(time.Minute * time.Duration(*IntervalToBatchWrite))
	for {
		select {
		case <-c.closed:
			return
		case <-tick:
			c.BatchPut()
		}
	}
}

func (c *Controller) BatchPut() {
	c.mu.Lock()
	rows := c.BufferRead()
	c.mu.Unlock()
	c.tables[c.dbname].BatchPut(rows)
}

func (c *Controller) BufferRead() [][]string {
	res := make([][]string, 0)
	for ip, ipMap := range c.buffer {
		for source, sourceMap := range ipMap {
			for state, timestamp := range sourceMap {
				res = append(res, []string{ip, source, state, timestamp})
			}
		}
	}
	c.buffer = make(RowSet)
	return res
}

func (buffer RowSet) setBuffer(ip, source, state string) {
	timestamp := time.Now().Format("2006-01-02 15:04:05")
	if buffer[ip] == nil {
		buffer[ip] = make(map[string]map[string]string)
	}
	if buffer[ip][source] == nil {
		buffer[ip][source] = make(map[string]string)
	}
	buffer[ip][source][state] = timestamp
}

func (c *Controller) BufferWrite(data map[string]string) {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.buffer.setBuffer(data["addr"], data["source"], data["state"])
}

func (c *Controller) Remove(ip, removed string) error {
	return c.tables[c.dbname].Remove(ip, removed)
}

func (c *Controller) GetStatesCount(interval int) (string, error) {
	return c.tables[c.dbname].GetStatesCount(interval)
}

func (c *Controller) GetAddrs() (string, error) {
	return c.tables[c.dbname].GetAddrs()
}

func (c *Controller) GetAddrsLimited(ver, vercmp, death, t string) (string, error) {
	return c.tables[c.dbname].GetAddrsLimited(ver, vercmp, death, t)
}

func (c *Controller) GetSources(addr string) (string, error) {
	return c.tables[c.dbname].GetSources(addr)
}

func (c *Controller) GetStatesArr(addr, source string) (string, error) {
	return c.tables[c.dbname].GetStatesArr(addr, source)
}

func (c *Controller) GetVersionsMap(addr, source string) (string, error) {
	return c.tables[c.dbname].GetVersionsMap(addr, source)
}

func (c *Controller) GetTimes(addr, source, state string) (string, error) {
	return c.tables[c.dbname].GetTimes(addr, source, state)
}
